package com.example.excel.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.excel.entity.User;
import com.example.excel.repository.UserMapper;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.FileOutputStream;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * Excel导出服务类
 * 提供SXSSF和XSSF两种方式导出大量数据的功能
 * 
 * SXSSF (Streaming XML Spreadsheet) 适用于大数据量导出，内存效率高
 * XSSF (XML Spreadsheet) 适用于小数据量导出，功能完整但内存占用高
 */
@Service
public class ExcelExportService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 日期时间格式化器
    private static final DateTimeFormatter DATE_FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    // 分页查询大小，每页查询10000条记录
    private static final int PAGE_SIZE = 10000;
    
    // Excel最大行数限制（Excel 2007+版本最大行数为1048576行）
    private static final int EXCEL_MAX_ROWS = 1048576;
    
    /**
     * 使用SXSSF方式导出大量数据到Excel
     * SXSSF是Apache POI提供的流式写入方式，适用于大数据量导出
     * 
     * @param filePath 导出文件路径
     * @return 导出的记录数
     * @throws IOException IO异常
     */
    public long exportWithSXSSF(String filePath) throws IOException {
        return exportWithSXSSF(filePath, 100);
    }
    
    /**
     * 使用SXSSF方式导出大量数据到Excel
     * SXSSF是Apache POI提供的流式写入方式，适用于大数据量导出
     * 
     * SXSSF特点：
     * 1. 内存中只保留指定行数的数据（rowAccessWindowSize）
     * 2. 超出部分会写入磁盘临时文件
     * 3. 适合处理百万级甚至更大数量的数据
     * 4. 导出完成后需要调用dispose()方法清理临时文件
     * 
     * @param filePath 导出文件路径
     * @param rowAccessWindowSize 内存中保留的行数，默认100行
     * @return 导出的记录数
     * @throws IOException IO异常
     */
    public long exportWithSXSSF(String filePath, int rowAccessWindowSize) throws IOException {
        long startTime = System.currentTimeMillis();
        System.out.println("开始使用SXSSF方式导出Excel...");
        
        // 创建SXSSFWorkbook对象，内存中只保留指定行数数据
        // 当数据行数超过rowAccessWindowSize时，旧数据会被写入临时文件
        SXSSFWorkbook workbook = new SXSSFWorkbook(rowAccessWindowSize);
        SXSSFSheet sheet = workbook.createSheet("用户数据");
        
        // 创建表头样式
        CellStyle headerStyle = createHeaderCellStyle(workbook);
        
        // 创建数据样式
        CellStyle dataStyle = createDataCellStyle(workbook);
        
        // 创建表头
        createHeaderRow(sheet, headerStyle);
        
        // 分页查询数据并写入Excel
        int rowNum = 1; // 数据从第二行开始（第一行为表头）
        int page = 1; // MyBatis Plus分页从1开始
        
        Page<User> userPage = new Page<>();
        userPage.setCurrent(page);
        userPage.setSize(PAGE_SIZE);
        userPage = userMapper.selectPage(userPage, null);
        long totalCount = userPage.getTotal();
        
        System.out.println("总共需要导出 " + totalCount + " 条数据");
        
        // 分页处理所有数据
        while (userPage.getRecords() != null && !userPage.getRecords().isEmpty()) {
            List<User> users = userPage.getRecords();
            
            // 写入当前页数据，但要确保不超过Excel的最大行数限制
            for (User user : users) {
                // 检查是否接近Excel行数限制
                if (rowNum >= EXCEL_MAX_ROWS) {
                    System.out.println("警告: 已达到Excel行数限制，停止导出");
                    break;
                }
                
                Row row = sheet.createRow(rowNum++);
                writeUserData(row, user, dataStyle);
            }
            
            System.out.println("已处理 " + (rowNum - 1) + " 条数据");
            
            // 如果已达到行数限制，则停止查询更多数据
            if (rowNum >= EXCEL_MAX_ROWS) {
                break;
            }
            
            // 如果已导出的数据达到总数据量，则停止查询更多数据
            if (rowNum > totalCount) {
                break;
            }
            
            // 查询下一页数据
            page++;
            userPage = new Page<>();
            userPage.setCurrent(page);
            userPage.setSize(PAGE_SIZE);
            userPage = userMapper.selectPage(userPage, null);
        }
        
        // 自动调整列宽，使内容完整显示
        for (int i = 0; i < 6; i++) {
            sheet.trackColumnForAutoSizing(i);
            sheet.autoSizeColumn(i);
        }
        
        // 写入文件
        try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
            workbook.write(outputStream);
        } finally {
            workbook.dispose(); // 释放临时文件，清理磁盘空间
        }
        
        long endTime = System.currentTimeMillis();
        System.out.println("SXSSF方式导出完成，共导出 " + (rowNum - 1) + " 条数据，耗时: " + (endTime - startTime) + " ms");
        
        return rowNum - 1;
    }
    
    /**
     * 使用XSSF方式导出数据到Excel（仅用于小数据量）
     * XSSF会将所有数据加载到内存中，不适用于大数据量导出
     * 
     * XSSF特点：
     * 1. 所有数据都保存在内存中
     * 2. 功能完整，支持所有Excel特性
     * 3. 大数据量时容易导致内存溢出(OutOfMemoryError)
     * 4. 适用于小数据量（建议小于10万条）的导出
     * 
     * @param filePath 导出文件路径
     * @return 导出的记录数
     * @throws IOException IO异常
     */
    public long exportWithXSSF(String filePath) throws IOException {
        long startTime = System.currentTimeMillis();
        System.out.println("开始使用XSSF方式导出Excel...");
        
        // 创建XSSFWorkbook对象，所有操作都在内存中进行
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("用户数据");
        
        // 创建表头样式
        CellStyle headerStyle = createHeaderCellStyle(workbook);
        
        // 创建数据样式
        CellStyle dataStyle = createDataCellStyle(workbook);
        
        // 创建表头
        createHeaderRow(sheet, headerStyle);
        
        // 查询所有数据并写入Excel
        int rowNum = 1; // 数据从第二行开始（第一行为表头）
        
        // 注意：对于百万级别的数据，这种方式可能会导致内存溢出
        // 这里仅用于演示XSSF的工作原理
        List<User> users = userMapper.selectList(null);
        long totalCount = users.size();
        
        System.out.println("总共需要导出 " + totalCount + " 条数据");
        
        // 控制导出的数据不超过实际数据量
        long exportedCount = 0;
        
        // 遍历所有用户数据并写入Excel
        for (User user : users) {
            // 检查是否已导出所有数据
            if (exportedCount >= totalCount) {
                break;
            }
            
            // 检查是否接近Excel行数限制
            if (rowNum >= EXCEL_MAX_ROWS) {
                System.out.println("警告: 已达到Excel行数限制，停止导出");
                break;
            }
            
            Row row = sheet.createRow(rowNum++);
            writeUserData(row, user, dataStyle);
            exportedCount++;
            
            // 每处理10000条数据打印一次进度
            if (rowNum % 10000 == 0) {
                System.out.println("已处理 " + (rowNum - 1) + " 条数据");
            }
        }
        
        // 自动调整列宽，使内容完整显示
        for (int i = 0; i < 6; i++) {
            sheet.autoSizeColumn(i);
        }
        
        // 写入文件
        try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
            workbook.write(outputStream);
        } finally {
            workbook.close(); // 关闭工作簿，释放内存
        }
        
        long endTime = System.currentTimeMillis();
        System.out.println("XSSF方式导出完成，共导出 " + (rowNum - 1) + " 条数据，耗时: " + (endTime - startTime) + " ms");
        
        return rowNum - 1;
    }
    
    /**
     * 创建表头样式
     * 表头使用粗体并居中对齐
     * 
     * @param workbook 工作簿对象
     * @return 表头样式
     */
    private CellStyle createHeaderCellStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true); // 设置粗体
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER); // 居中对齐
        return style;
    }
    
    /**
     * 创建数据单元格样式
     * 数据使用左对齐
     * 
     * @param workbook 工作簿对象
     * @return 数据样式
     */
    private CellStyle createDataCellStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.LEFT); // 左对齐
        return style;
    }
    
    /**
     * 创建表头行
     * 包含：用户ID、姓名、邮箱、年龄、地址、创建时间
     * 
     * @param sheet 工作表对象
     * @param headerStyle 表头样式
     */
    private void createHeaderRow(Sheet sheet, CellStyle headerStyle) {
        Row headerRow = sheet.createRow(0);
        
        Cell idCell = headerRow.createCell(0);
        idCell.setCellValue("用户ID");
        idCell.setCellStyle(headerStyle);
        
        Cell nameCell = headerRow.createCell(1);
        nameCell.setCellValue("姓名");
        nameCell.setCellStyle(headerStyle);
        
        Cell emailCell = headerRow.createCell(2);
        emailCell.setCellValue("邮箱");
        emailCell.setCellStyle(headerStyle);
        
        Cell ageCell = headerRow.createCell(3);
        ageCell.setCellValue("年龄");
        ageCell.setCellStyle(headerStyle);
        
        Cell addressCell = headerRow.createCell(4);
        addressCell.setCellValue("地址");
        addressCell.setCellStyle(headerStyle);
        
        Cell createTimeCell = headerRow.createCell(5);
        createTimeCell.setCellValue("创建时间");
        createTimeCell.setCellStyle(headerStyle);
    }
    
    /**
     * 将用户数据写入行
     * 按照表头顺序写入用户信息
     * 
     * @param row 行对象
     * @param user 用户对象
     * @param dataStyle 数据样式
     */
    private void writeUserData(Row row, User user, CellStyle dataStyle) {
        // 用户ID
        Cell idCell = row.createCell(0);
        idCell.setCellValue(user.getId());
        idCell.setCellStyle(dataStyle);
        
        // 用户姓名
        Cell nameCell = row.createCell(1);
        nameCell.setCellValue(user.getName());
        nameCell.setCellStyle(dataStyle);
        
        // 用户邮箱
        Cell emailCell = row.createCell(2);
        emailCell.setCellValue(user.getEmail());
        emailCell.setCellStyle(dataStyle);
        
        // 用户年龄（可能为空）
        Cell ageCell = row.createCell(3);
        if (user.getAge() != null) {
            ageCell.setCellValue(user.getAge());
        }
        ageCell.setCellStyle(dataStyle);
        
        // 用户地址
        Cell addressCell = row.createCell(4);
        addressCell.setCellValue(user.getAddress());
        addressCell.setCellStyle(dataStyle);
        
        // 创建时间（可能为空）
        Cell createTimeCell = row.createCell(5);
        if (user.getCreateTime() != null) {
            createTimeCell.setCellValue(user.getCreateTime().format(DATE_FORMATTER));
        }
        createTimeCell.setCellStyle(dataStyle);
    }
}